
local AceOO = AceLibrary("AceOO-2.0")

local Aura = AceLibrary("SpecialEvents-Aura-2.0")
local PaintChips = AceLibrary("PaintChips-2.0")

--=== State Attribute Generation functions ===--

-- generates a list of states from the description, and also generates prefixes and suffixes.
local function GenerateStateList(desc,i,min)
	min = (min or 1) - 1
	i = i or #desc
	if i == min then return {""} end
	local state = desc[i]
	local t = {}
	for s = state.min, state.max do
		for n,ss in ipairs(GenerateStateList(desc,i-1,min+1)) do
			if state["in_"..s] then
				for x,is in ipairs(GenerateStateList(state["in_"..s],nil)) do
					t[#t+1] = ss..state.short..s..is
				end
			else
				if s == state.default then
					t[#t+1] = ss
				else
					t[#t+1] = ss..state.short..s
				end
			end
		end
	end
	return t
end

local function GenerateLongStateList(desc,i,min)
	min = (min or 1) - 1
	i = i or #desc
	if i == min then return {""} end
	local state = desc[i]
	local t = {}
	for s = state.min, state.max do
		for n,ss in ipairs(GenerateLongStateList(desc,i-1,min+1)) do
			if state["in_"..s] then
				for x,is in ipairs(GenerateLongStateList(state["in_"..s],nil)) do
					t[#t+1] = ss..state.name..s..is.." "
				end
			else
				if s == state.default then
					t[#t+1] = ss
				else
					t[#t+1] = ss..state.name..s.." "
				end
			end
		end
	end
	return t
end

-- used by GenerateStateMap to generate the state transitions
local function GenerateTransitions(desc,i,n,parent)
	parent = parent or ""
	local prefixstates = GenerateStateList(desc,i-1,1)
	local suffixstates = GenerateStateList(desc,nil,i+1)
	local t = {}
	for a, prefix in ipairs(prefixstates) do
		for b, suffix in ipairs(suffixstates) do
			local from = {}
			local to
			if n == desc[i].default then
				to = parent..prefix..suffix
			else
				to = parent..prefix..desc[i].short..desc[i][n]..suffix
			end
			for c = desc[i].min, desc[i].max do
				if c ~= n and desc[i][c] == c and desc[i][c] ~= desc[i][n] then
					local tfrom
					if c == desc[i].default then
						tfrom = parent..prefix..suffix
					else
						tfrom = parent..prefix..desc[i].short..desc[i][c]..suffix
					end
					from[#from+1] = tfrom
				end
			end
			from = table.concat(from,",")
			t[#t+1] = from..":"..to
		end
	end
	return table.concat(t,";")
end

-- this function is used by GenerateStateAttributes to create the stancemap- entries.
local function GenerateStateMap(desc,t,parent)
	local t = t or {}
	for i,state in ipairs(desc) do
		for n = state.min,state.max do
			t["statemap-"..state.name.."-"..n]=GenerateTransitions(desc,i,n,parent)
			if state["in_"..n] then
				GenerateStateMap(state["in_"..n],t,state.short..n)
			end
		end
	end
	return t
end

-- generates current state from the settings in the descriptor.
local function GenerateCurrentState(desc,t,notable)
	if not notable then
		t = t or {}
	end
	local s = ""
	for i,state in ipairs(desc) do
		local snum
		if state.statefunc then
			snum = getglobal(state.statefunc)(state.statearg)
		end
		if not snum then
			if state.stateconst then
				snum = state.stateconst
			else
				snum = state.min
			end
		end
		s = s..state.short..state[snum]
	end
	if notable then return s end
	t["state"]=s
	return t
end

-- the following function returns all of the attributes needed to set up a header properly, including initial state.
-- the attributes are contained in a table of key-value entries.
local function GenerateStateAttributes(desc)
	t = GenerateStateMap(desc)
	local l = GenerateStateList(desc)
	for i,v in ipairs(l) do l[i] = v..":"..v end
	t["statebutton"]=table.concat(l,";")
	return GenerateCurrentState(desc,t)
end

local function GenerateDescriptorFromOptions(options)
	local desc = {}
	if (options.pages or 1) > 1 then -- A
		desc[#desc+1] = {name="actionbar", min=1, max=options.pages, short="A", stateconst=1}
		for i = 1, options.pages do
			desc[#desc][i] = i
		end
	end
	if options.stance then -- F
		local class = select(2,UnitClass("player"))
		if class == "WARRIOR" then
			desc[#desc+1] = {name="stance", min=1, max=3, short="F", statefunc='GetShapeshiftForm', statearg=true,
				[1]=1, [2]=2, [3]=3}
		elseif class == "DRUID" then
			desc[#desc+1] = {name="stance", min=0, max=7, short="F", statefunc='GetShapeshiftForm', statearg=true,
				[0]=0, [1]=1, [2]=0, [3]=3, [4]=0, [5]=5, [6]=6, [7]=7, in_3={{name="stealth", min=0, max=1, short="S",
				[0]=0, [1]=1, default=0, statefunc="IsStealthed"}}}
		elseif class == "PRIEST" then
			desc[#desc+1] = {name="stance", min=0, max=1, short="F", statefunc='GetShapeshiftForm', statearg=true,
				[0]=0, [1]=1}
		end
	end
	if #desc == 0 then -- A
		desc[#desc+1] = {name="actionbar", min=1, max=1, short="A", stateconst=1, [1]=1}
	end
	if options.stealth then -- S
		desc[#desc+1] = {name="stealth", min=0, max=1, short="S", statefunc='IsStealthed', [0]=0, [1]=1}
	end
	if options.shift then -- T
		desc[#desc+1] = {name="shift", min=0, max=1, short="T", statefunc='IsShiftKeyDown', [0]=0, [1]=1}
	end
	if options.ctrl then -- C
		desc[#desc+1] = {name="control", min=0, max=1, short="C", statefunc='IsControlKeyDown', [0]=0, [1]=1}
	end
	if options.alt then -- L
		desc[#desc+1] = {name="alt", min=0, max=1, short="L", statefunc='IsAltKeyDown', [0]=0, [1]=1}
	end
	return desc
end

--=== Basic initialization of the IBInfiniBar object ===--

local IBInfiniBar = AceOO.Class(IIB_BarBase,"AceEvent-2.0")

function IBInfiniBar.prototype:init(name)
	self.class.super.prototype.init(self)
	if type(name) ~= "table" then
		self.name = name
		self.type = "IBInfiniBar-1.0"
		self.btn = {}
		self.btndata = {}
		self.btnframe = {}
		self.hdrState = {}
		self.temp = {}
		InfiniBar:RegisterBar(self)
		self:SetBarTheme("IBDefaultTheme-1.0")
	else
		for k,v in pairs(name) do
			self[k] = v
		end
		self.btn = self.btn or {}
		self.btndata = self.btndata or {}
		self.btnframe = {}
		self.temp = {}
		self.themetype = self.themetype or "IBDefaultTheme-1.0"
		self:SetBarTheme(self.themetype)
		InfiniBar:RegisterBar(self)
		self:DefineStates(self.hdrStateDesc)
		self:UpdateHeaderState()
		self:UpdateAllButtonsStates()
		self:UpdateTexts()
		self:ResetButtonBindings()
	end
end

--=== Theme Setup ===--

function IBInfiniBar.prototype:SetBarTheme(theme,update)
	if update and self.theme then
		for i, button in ipairs(self.btnframe) do
			self.theme:DisableTheme(button)
		end
	end
	if type(theme) == "string" then
		local tTheme = AceLibrary(theme):new()
		if AceOO.inherits(tTheme,IIB_Theme) then
			self.theme = tTheme
			self.themetype = theme
		else
			self.theme = AceLibrary("IBDefaultTheme-1.0"):new()
			self.themetype = "IBDefaultTheme-1.0"
		end
	else
		error("theme is a "..type(theme))
	end
	if update then
		for i, button in ipairs(self.btnframe) do
			self.theme:EnableTheme(button)
		end
	end
end

function IBInfiniBar.prototype:RegisterButtonScript(btn,script,methodName)
	local button = self.btnframe[btn]
	if button then
		local method = self[methodName]
		local oldScript = button:GetScript(script)
		if oldScript then
			button:SetScript(script,function(...)
				oldScript(...)
				method(self,btn,...)
			end)
		else
			button:SetScript(script,function(...)
				method(self,btn,...)
			end)
		end
	end
end

function IBInfiniBar.prototype:TriggerBarEvent(event,...)
	if type(self.temp.events[event]) == "function" then
		self.temp.events[event](...)
	end
end

function IBInfiniBar.prototype:UnregisterBarEvent(event)
	self.temp.events[event] = nil
end

function IBInfiniBar.prototype:RegisterBarEvent(event,method)
	InfiniBar:RegisterBarEvent(event)
	self.temp.events = self.temp.events or {}
	self.temp.events[event] = method
	if type(method) == "string" and type(self[method]) == "function" then
		self.temp.events[event] = function(...)
			self[method](self,...)
		end
	end
end

function IBInfiniBar.prototype:RefreshStates(options)
	local desc = GenerateDescriptorFromOptions(options)
	for attr,val in pairs(GenerateStateAttributes(desc)) do
		self.header:SetAttribute(attr,val)
	end
	self.hdrStateDesc = options
	self:UpdateHeaderState()
end

function IBInfiniBar.prototype:DefineStates(options)
	self.header = CreateFrame("Frame", "IB_"..self.name.."_header", UIParent, "SecureStateDriverTemplate")
	self:RefreshStates(options)
	self.header:Show()
	self.header:SetMovable(true)
	--self.header.StateChanged = function(hdr,newState)
		--DEFAULT_CHAT_FRAME:AddMessage("IB_"..self.name.."_header state changed to: "..newState)
	--end
	--self.temp.abRotate = CreateFrame("Button", "IB_"..self.name.."_header_Rotate", nil, "SecureActionButtonTemplate")
	self:RegisterBarEvent("SpecialEvents_PlayerBuffGained", "HighlightBuff")
	self:RegisterBarEvent("SpecialEvents_PlayerBuffLost", "HighlightBuff")
	self:RegisterBarEvent("UNIT_INVENTORY_CHANGED", "HighlightGear")
	self:RegisterBarEvent("SpecialEvents_UnitDebuffGained", "HighlightDebuff")
	self:RegisterBarEvent("SpecialEvents_UnitDebuffLost", "HighlightDebuff")
	self:RegisterBarEvent("SpecialEvents_AuraTargetChanged", "HighlightDebuff")
	self:RegisterBarEvent("START_AUTOREPEAT_SPELL","HighlightAutoCasting")
	self:RegisterBarEvent("STOP_AUTOREPEAT_SPELL","HighlightAutoCasting")
	self:RegisterBarEvent("PLAYER_ENTER_COMBAT",function() self.temp.attackon = true self:HighlightAutoCasting() end)
	self:RegisterBarEvent("PLAYER_LEAVE_COMBAT",function() self.temp.attackon = nil self:HighlightAutoCasting() end)
	self:RegisterBarEvent("IB_OverlayUpdate", "OverlayUpdate")
	self:RegisterBarEvent("UNIT_MANA", "OverlayUsable")
	self:RegisterBarEvent("UNIT_RAGE", "OverlayUsable")
	self:RegisterBarEvent("UNIT_ENERGY", "OverlayUsable")
	self:RegisterBarEvent("BAG_UPDATE", "OverlayItem")
	self:ScheduleRepeatingEvent("IB_OverlayUpdate",0.5)
	self:RegisterBarEvent("UPDATE_SHAPESHIFT_FORMS", function()
		for i in ipairs(self.btn) do
			self:UpdateButton(i)
		end
	end)
	--self.temp.curState = self.header:GetAttribute("state")
	--self.header:SetScript("OnAttributeChanged",function()
		--local curState = self.header:GetAttribute("state")
		--if curState ~= self.temp.curState then
			--for i in ipairs(self.btn) do
				--self:UpdateButton(i)
			--end
		--end
	--end)
end

function IBInfiniBar.prototype:SetButtonScale(scale,btn)
	scale = scale or 1
	if btn and self.btn[btn] then
		self.btn[btn].scale = scale
		self:UpdateButtonStates(btn,"scale")
	end
end

function IBInfiniBar.prototype:SetButtonSize(width,height,btn)
	width = width or 36
	height = height or width
	if btn and self.btn[btn] then
		self.btn[btn].width = width
		self.btn[btn].height = height
		-- update button state.
		self:UpdateButtonState(btn,"width","height")
	end
end

function IBInfiniBar.prototype:SetButtonLocation(x,y,btn)
	if btn and self.btn[btn] then
		self.btn[btn].ofsx = x or 0
		self.btn[btn].ofsy = y or 0
		--update Button state
		self:UpdateButtonState(btn,"ofsx","ofsy")
	end
end

function IBInfiniBar.prototype:SetPosition(x,y,point,relpoint)
	self.hdrState.headofsx = x
	self.hdrState.headofsy = y
	self.hdrState.headofspoint = point
	self.hdrState.headofsrelpoint = relpoint
	self:UpdateHeaderState("headofsx","headofsy","headofspoint","headofsrelpoint")
end

function IBInfiniBar.prototype:NewButton()
	self.btn[#self.btn+1] = {}
	self.btndata[#self.btn] = {}
	self:UpdateButtonState(#self.btn) -- this creates the button if it doesn't exist, which is usually the case here.
	return #self.btn
end

function IBInfiniBar.prototype:DeleteButton(btn)
	self.btnframe[btn]:SetAttribute('hidestates','*')
	self.btnframe[btn]:SetAttribute('showstates',nil)
	self.btnframe[btn]:Hide()
	local lastBinding = self.btndata[btn].bind
	if lastBinding then
		SetBinding(lastBinding)
	end
	table.remove(self.btn,btn)
	table.remove(self.btnframe,btn)
	table.remove(self.btndata,btn)
end

function IBInfiniBar.prototype:DeleteBar()
	for i = #self.btn, 1, -1 do
		self:DeleteButton(i)
	end
	self.header:Hide()
end

function IBInfiniBar.prototype:UpdateHeaderState(...)
	local t
	if select("#",...) == 0 then
		t = {}
		for k in pairs(self.hdrState) do
			t[#t+1] = k
		end
	else
		t = {...}
	end
	for i,attr in ipairs(t) do local val = self.hdrState[attr]
		self.header:SetAttribute(attr,val)
		if attr == "headofsx" or attr == "headofsy" or attr == "headofspoint" or attr == "headofsrelpoint" then
			self.header:ClearAllPoints()
			self.header:SetPoint(self.hdrState.headofspoint or "CENTER",UIParent,
				self.hdrState.headofsrelpoint or self.hdrState.headofspoint or "CENTER", 
				self.hdrState.headofsx or 0, self.hdrState.headofsy or 0)
		end
	end
end

function IBInfiniBar.prototype:UpdateAllButtonsStates(...)
	for i = 1, #self.btn do
		self:UpdateButtonState(i,...)
	end
end

local textAnchor = {"TOPLEFT","TOPRIGHT","BOTTOMRIGHT","BOTTOMLEFT","CENTER"}
local textJustify = {"LEFT","RIGHT","RIGHT","LEFT","CENTER"}
local textName = {"HotKey","text2","Count","Name","text5"}
local textLayer = {"OVERLAY","OVERLAY","OVERLAY","OVERLAY","OVERLAY"}
local textInherits = {"NumberFontNormalSmallGray","NumberFontNormal","NumberFontNormal","GameFontHighlightSmallOutline","NumberFontNormal"}

function IBInfiniBar.prototype:UpdateButtonState(btn,...)
	local button = self.btnframe[btn]
	if not button then
		local btnname = "IB"..self.name.."_"..btn
		self.btnframe[btn] = CreateFrame("CheckButton", btnname, UIParent, "SecureActionButtonTemplate")
		button = self.btnframe[btn]
		button:SetWidth(36)
		button:SetHeight(36)
		button:SetFrameStrata("MEDIUM")
		button:RegisterForDrag("LeftButton")
		button.Icon = button:CreateTexture(btnname.."Icon", "BACKGROUND")
		button.Icon:SetAllPoints(button)
		button.Flash = button:CreateTexture(btnname.."Flash", "ARTWORK")
		button.Flash:SetAllPoints(button)
		button.Flash:Hide()
		button.Cooldown = CreateFrame("Cooldown", btnname.."Cooldown", button, "CooldownFrameTemplate")
		self.theme:EnableTheme(button)
		button:RegisterForClicks("AnyUp")
		button:EnableMouse(true)
		button:SetClampedToScreen(true)
		button:SetMovable(true)
		self.header:SetAttribute("addchild",button)
		button:Show()
		button:SetAttribute("useparent-statebutton",true)
		for i = 1, 5 do
			local Text = button:CreateFontString(btnname..textName[i], "ARTWORK")
			Text:SetFontObject(textInherits[i])
			Text:SetJustifyH(textJustify[i])
			Text:SetPoint(textAnchor[i], button, textAnchor[i])
			Text:SetDrawLayer(textLayer[i])
			button['Text'..i] = Text
		end
		-- Register Scripts, e.g. OnAttributeChanged
		--self:RegisterButtonScript(btn,"PreClick","PreClickRegister")
		self:RegisterButtonScript(btn,"PreClick","DragHack")
		self:RegisterButtonScript(btn,"PostClick","DragUnhack")
		self:RegisterButtonScript(btn,"OnDragStart","DragStart")
		self:RegisterButtonScript(btn,"OnDragStop","DragEnd")
		self:RegisterButtonScript(btn,"OnReceiveDrag","DragStop")
		self:RegisterButtonScript(btn,"OnAttributeChanged","UpdateButton")
		self:RegisterButtonScript(btn,"OnEnter","ShowButtonTooltip")
		self:RegisterButtonScript(btn,"OnLeave","HideButtonTooltip")
	end
	local t
	if select("#",...) == 0 then
		t = {}
		for k in pairs(self.btn[btn]) do
			t[#t+1] = k
		end
	else
		t = {...}
	end
	for i,attr in ipairs(t) do local val = self.btn[btn][attr]
		--DEFAULT_CHAT_FRAME:AddMessage("SetButtonAttribute: "..tostring(btn)..', '..tostring(button)..', '..tostring(attr))
		button:SetAttribute(attr,val)
		if attr == "width" then
			button:SetWidth(val)
		elseif attr == "height" then
			button:SetHeight(val)
		elseif attr == "scale" then
			button:SetScale(val)
		elseif attr == "ofsx" or attr == "ofsy" or attr == "ofspoint" or attr == "ofsrelpoint" then
			button:ClearAllPoints()
			button:SetPoint(self.btn[btn].ofspoint or "CENTER",self.header,
				self.btn[btn].ofsrelpoint or self.btn[btn].ofspoint or "CENTER", 
				self.btn[btn].ofsx or 0, self.btn[btn].ofsy or 0)
		end
	end
end

function IBInfiniBar.prototype:PreClickRegister()
	self:RegisterBarEvent("UNIT_SPELLCAST_START", "SpellCastStart")
	self:RegisterBarEvent("UNIT_SPELLCAST_CHANNEL_START", "ChannelingStart")
end

function IBInfiniBar.prototype:SpellCastStart(unit)
	if unit ~= "player" then return end
	for i,button in ipairs(self.btnframe) do
		local sName = self:GetButtonEffectiveState(i,"spell")
		if not sName then
			-- check to see if this is a macro, and if so, whether it is the spell being cast.
			local sMacroID = self:GetButtonEffectiveState(i,"macro")
			if not sMacroID then
				sMacroID = self:GetButtonEffectiveState(i,"macrotext")
			end
			if sMacroID then
				local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
				if mType == "spell" then
					sName = mData
				end
			end
		end
		local spell, rank = UnitCastingInfo("player")
		if sName == spell..'('..rank..')' then
			self.temp.curcastbutton = button
			self.temp.curcastbtn = i
			self:UnregisterBarEvent("UNIT_SPELLCAST_START")
			self:UnregisterBarEvent("UNIT_SPELLCAST_CHANNEL_START")
			self:RegisterBarEvent("UNIT_SPELLCAST_STOP", "SpellCastStop")
			-- highlight the button appropriately.
			self:SetFlash(i,"Casting",true)
			break
		end
	end
end

function IBInfiniBar.prototype:SpellCastStop(unit)
	if unit ~= "player" then return end
	button = self.temp.curcastbutton
	self.temp.curcastbutton = nil
	self:UnregisterBarEvent("UNIT_SPELLCAST_STOP")
	-- unhighlight the button appropriately.
	self:SetFlash(self.temp.curcastbtn,"Casting",false)
	self.temp.curcastbtn = nil
end

function IBInfiniBar.prototype:ChannelingStart(unit)
	if unit ~= "player" then return end
	for i,button in ipairs(self.btnframe) do
		local sName = self:GetButtonEffectiveState(i,"spell")
		if not sName then
			-- check to see if this is a macro, and if so, whether it is the spell being cast.
			local sMacroID = self:GetButtonEffectiveState(i,"macro")
			if not sMacroID then
				sMacroID = self:GetButtonEffectiveState(i,"macrotext")
			end
			if sMacroID then
				local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
				if mType == "spell" then
					sName = mData
				end
			end
		end
		local spell, rank = UnitChannelInfo("player")
		if sName == spell..'('..rank..')' then
			local spellID = InfiniBar:GetSpellInfo(sName)
			if IsCurrentCast(spellID,"spell") then
				self.temp.curcastbutton = button
				self:UnregisterBarEvent("UNIT_SPELLCAST_START")
				self:UnregisterBarEvent("UNIT_SPELLCAST_CHANNEL_START")
				self:RegisterBarEvent("UNIT_SPELLCAST_CHANNEL_STOP", "ChannelingStop")
				-- highlight the button appropriately.
				self:SetFlash(i,"Channeling",true)
				break
			end
		end
	end
end

function IBInfiniBar.prototype:ChannelingStop(unit)
	if unit ~= "player" then return end
	button = self.temp.curcastbutton
	self.temp.curcastbutton = nil
	self:UnregisterBarEvent("UNIT_SPELLCAST_CHANNEL_STOP")
	-- unhighlight the button appropriately.
	self:SetFlash(self.temp.curcastbtn,"Channeling",false)
	self.temp.curcastbtn = nil
end

-- this sets the given attribute and value on the given button
function IBInfiniBar.prototype:SetButtonAttribute(btn,attr,val)
	self.btn[btn][attr] = val
	self:UpdateButtonState(btn,attr)
end

function IBInfiniBar.prototype:SetButtonMetaData(btn,attr,val)
	self.btndata = self.btndata or {}
	self.btndata[btn] = self.btndata[btn] or {}
	self.btndata[btn][attr] = val
end

-- this sets the given attribute and value on the given button for the header's current state.
function IBInfiniBar.prototype:SetButtonAttributeCurState(btn,attr,val)
	local curState = self.header:GetAttribute("state")
	if curState and attr then
		self:SetButtonAttribute(btn,attr..'-'..curState,val)
	end
end

function IBInfiniBar.prototype:SetButtonMetaDataCurState(btn,attr,val)
	local curState = self.header:GetAttribute("state")
	if curState and attr then
		self:SetButtonMetaData(btn,attr..'-'..curState,val)
	end
end

local typeMap = {spell="spell",pet="action",item="item",macro="macro"}

function IBInfiniBar.prototype:DragStart(btn)
	-- check to see if the bar is draggable.
	if self.barDraggable then
		self.header:StartMoving()
		self.temp.barMoving = true
		return
	elseif self.btndata[btn] and self.btndata[btn].draggable then
		self.btnframe[btn]:StartMoving()
		self.temp.btnMoving = btn
		return
	end
	-- Check to see if button contents are locked.  if so, simply return.
	if self.btndata[btn] and self.btndata[btn].contentsLock then return end
	local bType = self:GetButtonEffectiveState(btn,"type")
	if bType == "spell" then
		PickupSpell(InfiniBar:GetSpellInfo(self:GetButtonEffectiveState(btn,typeMap[bType])),"spell")
		self:SetButtonAttributeCurState(btn,"type",nil)
		self:SetButtonAttributeCurState(btn,"spell",nil)
	elseif bType == "pet" then
		PickupPetAction(self:GetButtonEffectiveState(btn,"pet"))
		self:SetButtonAttributeCurState(btn,"type",nil)
		self:SetButtonAttributeCurState(btn,"pet",nil)
	elseif bType == "macro" and self:GetButtonEffectiveState(btn,"macro") then
		PickupMacro(self:GetButtonEffectiveState(btn,"macro"))
		self:SetButtonAttributeCurState(btn,"type",nil)
		self:SetButtonAttributeCurState(btn,"macro",nil)
	end
	self.temp.flash = self.temp.flash or {}
	self.temp.flashon = self.temp.flashon or {}
	self.temp.flash[btn] = {}
	self.temp.flashon[btn] = {}
	local button = self.btnframe[btn]
	button.Flash:Hide()
	button.Icon:SetVertexColor(1,1,1)
	button.Cooldown:Hide()
	--self:UpdateButton(btn)
end

function IBInfiniBar.prototype:DragEnd()
	if self.temp.barMoving then
		local frame = self.header
		local p ,rel ,rp ,X ,Y = frame:GetPoint()
		frame:StopMovingOrSizing()
		self.temp.barMoving = nil
		self.hdrState.headofsx = X
		self.hdrState.headofsy = Y
	elseif self.temp.btnMoving then
		local frame = self.btnframe[self.temp.btnMoving]
		local uiscale = UIParent:GetEffectiveScale()
		local buttonscale = self.btn[self.temp.btnMoving].scale
		frame:StopMovingOrSizing()
		local p ,rel ,rp ,X ,Y = frame:GetPoint()
		self:SetButtonLocation((((X - 18) * uiscale / buttonscale) - self.hdrState.headofsx) * buttonscale / uiscale,
			(((Y + 18) * uiscale / buttonscale) - self.hdrState.headofsy) * buttonscale / uiscale,self.temp.btnMoving)
		--self:SetButtonAttribute(self.temp.btnMoving].ofsx = (((X - 18) * uiscale / buttonscale) - self.hdrState.headofsx) * buttonscale / uiscale
		--self.btn[self.temp.btnMoving].ofsy = (((Y + 18) * uiscale / buttonscale) - self.hdrState.headofsy) * buttonscale / uiscale
		self.temp.btnMoving = nil
	end
end

-- the following two functions are necessary to prevent Blizz's secure OnClick handler from trying to use the action in the button when dropping an action.
-- this essentially saves the type of action out, sets it nil, and restores it before calling DragStop.
function IBInfiniBar.prototype:DragHack(btn)
	self:PreClickRegister(btn)
	if (GetCursorInfo()) and not self.temp.draghack then
		self.temp.draghack = true
		self.temp.dragtype = self:GetButtonEffectiveState(btn,"type")
		self:SetButtonAttributeCurState(btn,"type",nil)
		--self.temp.dragattr[typeMap[self.temp.dragattr["type"]] = self:GetButtonEffectiveState(btn,self.temp.dragattr["type"])
		--if self.temp.dragattr["type"] == "macro" then
	end
end

function IBInfiniBar.prototype:DragUnhack(btn)
	if self.temp.draghack then
		self.temp.draghack = nil
		--self.btnframe[btn]:SetScript("OnClick",self.temp.savedOnClick)
		--self:UnregisterEvent("IBS_OnClick")
		self:SetButtonAttributeCurState(btn,"type",self.temp.dragtype)
		self:DragStop(btn)
	end
	self:HighlightBuff()
	self:HighlightGear()
	self:HighlightDebuff()
	self:HighlightAutoCasting()
end

-- Drag Stop is the point when cursor items are placed into buttons.
function IBInfiniBar.prototype:DragStop(btn)
	-- Check to see if button contents are locked.  if so, simply return.
	if self.btndata[btn] and self.btndata[btn].contentsLock then return end
	local aType,aData,aData2 = GetCursorInfo()
	if not aType then return end
	-- first get the last type to eliminate, so we can set it's attribute to nil if we overwrite it.
	local bType = self:GetButtonEffectiveState(btn,"type")
	local bData
	if bType then
		bData = self:GetButtonEffectiveState(btn,typeMap[bType])
	end
	if aType == "spell" and aData2 == "spell" then
		ClearCursor()
		local sName, sRank = GetSpellName(aData,aData2)
		sName = sName..'('..sRank..')'
		if typeMap[bType] ~= "spell" then self:SetButtonAttributeCurState(btn,typeMap[bType],nil) end
		if bType == "macro" then self:SetButtonAttributeCurState(btn,"macrotext",nil) end
		self:SetButtonAttributeCurState(btn,"type","spell")
		self:SetButtonAttributeCurState(btn,aData2,sName)
	elseif aType == "spell" and aData2 == "pet" then
		ClearCursor()
		if typeMap[bType] ~= "action" then self:SetButtonAttributeCurState(btn,typeMap[bType],nil) end
		if bType == "macro" then self:SetButtonAttributeCurState(btn,"macrotext",nil) end
		self:SetButtonAttributeCurState(btn,"type","pet")
		self:SetButtonAttributeCurState(btn,aData2,aData)
	elseif aType == "item" then
		ClearCursor()
		local iName, _, _, _, _, _, _, _, _, iTex = GetItemInfo(aData2)
		if typeMap[bType] ~= "item" then self:SetButtonAttributeCurState(btn,typeMap[bType],nil) end
		if bType == "macro" then self:SetButtonAttributeCurState(btn,"macrotext",nil) end
		self:SetButtonAttributeCurState(btn,"type","item")
		self:SetButtonAttributeCurState(btn,aType,iName)
		self:SetButtonMetaDataCurState(btn,aType,aData2) -- store the actual item hyperlink for later use.
		self:SetButtonMetaDataCurState(btn,"itemTexture",iTex) -- store the texture for later use also.
	elseif aType == "macro" then
		ClearCursor()
		if typeMap[bType] ~= "macro" then self:SetButtonAttributeCurState(btn,typeMap[bType],nil) end
		if bType == "macro" then self:SetButtonAttributeCurState(btn,"macrotext",nil) end
		self:SetButtonAttributeCurState(btn,"type","macro")
		self:SetButtonAttributeCurState(btn,aType,aData)
	end
	if bType == "spell" then
		local index = InfiniBar:GetSpellInfo(bData)
		if index then
			PickupSpell(index,"spell")
		end
	elseif bType == "pet" then
		PickupPetAction(bData)
	elseif bType == "macro" and bData then
		PickupMacro(bData)
	end
end

function IBInfiniBar.prototype:GetButtonEffectiveState(btn,attr)
	if type(btn) == 'string' then
		-- if btn is a string, then this function is being called like (attr,btn) instead. swap argument values
		attr,btn = btn,attr
	end
	local button = self.btn[btn]
	if not button then return end
	local curState = self.header:GetAttribute("state")
	return button[attr.."-"..curState]
end

function IBInfiniBar.prototype:GetButtonEffectiveMetaData(btn,attr)
	if type(btn) == 'string' then
		-- if btn is a string, then this function is being called like (attr,btn) instead. swap argument values
		attr,btn = btn,attr
	end
	local button = self.btndata[btn]
	if not button then return end
	local curState = self.header:GetAttribute("state")
	return button[attr.."-"..curState]
end

function IBInfiniBar.prototype:GetButtonTexture(btn)
	self.temp.btncache = self.temp.btncache or {}
	self.temp.btncache[btn] = {}
	local button = self.btnframe[btn]
	if not button then return end
	local newTexture
	
	-- Find out if there is a valid override for this button in the current state
	local curState = self.header:GetAttribute("state")
	local ovrtype = self.btndata[btn]['ovrtype-'..curState]
	if ovrtype and self.btndata[btn]['ovrIcon-'..curState] then
		local ovraction = self.btndata[btn]['ovr'..ovrtype..'-'..curState]
		if ovrtype == 'spell' and ovraction then
			newTexture = GetSpellTexture(ovraction)
		elseif ovrtype == 'pet' and ovraction then
			newTexture = select(3,GetPetActionInfo(ovraction))
		elseif ovrtype == 'item' and ovraction then
			newTexture = select(10,GetItemInfo(ovraction))
		end
		if newTexture then
			return newTexture
		end
	end
	newTexture = ""
	
	local bType = self:GetButtonEffectiveState(btn,"type")
	if not bType then return end
	local bAction = self:GetButtonEffectiveState(btn,typeMap[bType])
	if bType == "spell" then
		if not bAction then return end
		newTexture = GetSpellTexture(bAction)
	elseif bType == "pet" then
		if not bAction then return end
		newTexture = select(3,GetPetActionInfo(bAction))
	elseif bType == "item" then
		if not bAction then return end
		newTexture = select(10,GetItemInfo(bAction)) or self:GetButtonEffectiveMetaData(btn,"itemTexture")
	elseif bType == "macro" then
		if bAction then
			newTexture = IB_GetMacroIcon(bAction)
			if not newTexture then
				newTexture = (select(2,GetMacroInfo(bAction)))
				if newTexture == "Interface\\Icons\\INV_Misc_QuestionMark" then
					newTexture = nil
				end
			end
		else
			bAction = self:GetButtonEffectiveState(btn,"macrotext")
			if not bAction then return end
			newTexture = IB_GetMacroIcon(bAction)
		end
	end
	return newTexture
end

function IBInfiniBar.prototype:UpdateButton(btn)
	-- clear the effective attribute cache
	local button = self.btnframe[btn]
	if not button then return end
	local newTexture = self:GetButtonTexture(btn)
	if newTexture then
		button.Icon:SetTexture(newTexture)
		button.Icon:Show()
	else
		button.Icon:Hide()
	end
	-- Now clear the temporary flash and overlay caches as needed.
	--local curState = self.header:GetAttribute("state")
	if self.temp.refresh then
		self.temp.flash = {}
		self.temp.flashon = {}
		self.temp.ovrly = {}
		self.temp.ovrlyon = {}
	end
	self:HighlightBuff()
	self:HighlightGear()
	self:HighlightDebuff()
	self:HighlightAutoCasting()
	self:OverlayUpdate()
	self:OverlayUsable()
	self:OverlayItem()
	self:UpdateTexts()
end

function IBInfiniBar.prototype:RefreshButtonTooltip(btn)
	local button = self.btnframe[btn]
	if not button then return end
	local bType = self:GetButtonEffectiveState(btn,"type")
	if not bType then return end
	local bAction = self:GetButtonEffectiveState(btn,typeMap[bType])

	local curState = self.header:GetAttribute("state")
	local ovrtype = self.btndata[btn]['ovrtype-'..curState]
	if ovrtype and self.btndata[btn]['ovrTooltip-'..curState] then
		local ovraction = self.btndata[btn]['ovr'..ovrtype..'-'..curState]
		if ovraction then
			bType = ovrtype
			bAction = ovraction
		end
	end
	local doUpdate
	if bType == "spell" then
		if not bAction then return end
		local index = InfiniBar:GetSpellInfo(bAction)
		if index then
			if GetCVar("UberTooltips") == "1" then
				GameTooltip_SetDefaultAnchor(GameTooltip, button)
			else
				GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
			end
			GameTooltip:SetSpell(index,"spell")
			doUpdate = true
		end
	elseif bType == "pet" then
		if not bAction then return end
		if GetCVar("UberTooltips") == "1" then
			GameTooltip_SetDefaultAnchor(GameTooltip, button)
		else
			GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
		end
		GameTooltip:SetPetAction(bAction)
		doUpdate = true
	elseif bType == "item" then
		if not bAction then return end
		if GetCVar("UberTooltips") == "1" then
			GameTooltip_SetDefaultAnchor(GameTooltip, button)
		else
			GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
		end
		bAction = self:GetButtonEffectiveMetaData(btn,typeMap[bType])
		local _, _, iLink = string.find(bAction,"|H(.+)|h%[")
		GameTooltip:SetHyperlink(iLink)
		doUpdate = true
	elseif bType == "macro" then
		if not bAction then bAction = self:GetButtonEffectiveState(btn,"macrotext") end
		if not bAction then return end
		if GetCVar("UberTooltips") == "1" then
			GameTooltip_SetDefaultAnchor(GameTooltip, button)
		else
			GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
		end
		IB_SetMacroTooltip(bAction)
		doUpdate = true
	end
	if not doUpdate then return end
	GameTooltip:Show()
	return true
end

function IBInfiniBar.prototype:ShowButtonTooltip(btn)
	if not self:RefreshButtonTooltip(btn) then return end
	self:RegisterEvent("IB_TooltipUpdate","UpdateButtonTooltip")
	self.temp.ttupdate = self:ScheduleRepeatingEvent("IB_TooltipUpdate",0.5,btn)
end

function IBInfiniBar.prototype:HideButtonTooltip(btn)
	if self.temp.ttupdate then
		GameTooltip:Hide()
		self:CancelScheduledEvent(self.temp.ttupdate)
		self.temp.ttupdate = nil
		self:UnregisterEvent("IB_TooltipUpdate")
	end
end

function IBInfiniBar.prototype:UpdateButtonTooltip(btn)
	local button = self.btnframe[btn]
	if not button then return end
	if ( GameTooltip:IsOwned(button) ) then
		self:RefreshButtonTooltip(btn)
	elseif self.temp.ttupdate then
		self:CancelScheduledEvent(self.temp.ttupdate)
		self.temp.ttupdate = nil
		self:UnregisterEvent("IB_TooltipUpdate")
	end
end

function IBInfiniBar.prototype:SetFlash(flashcode,set,btn)
	if not InfiniBar.flashPriority[flashcode] then return end
	local button = self.btnframe[btn]
	if not button then return end
	self.temp.flash = self.temp.flash or {}
	self.temp.flashon = self.temp.flashon or {}
	self.temp.flash[btn] = self.temp.flash[btn] or {}
	self.temp.flashon[btn] = self.temp.flashon[btn] or {}
	if set then
		if not self.temp.flashon[btn][flashcode] then
			self.temp.flash[btn][#self.temp.flash[btn]+1] = flashcode
			table.sort(self.temp.flash[btn],function(a,b) return InfiniBar.flashPriority[a] < InfiniBar.flashPriority[b] end)
			self.temp.flashon[btn][flashcode] = true
		end
	elseif self.temp.flashon[btn][flashcode] then
		for i,v in ipairs(self.temp.flash[btn]) do if v == flashcode then table.remove(self.temp.flash[btn],i) break end end
		self.temp.flashon[btn][flashcode] = nil
	end
	if self.temp.flash[btn][1] then
		-- turn on the highest priority flash
		local r,g,b = unpack(InfiniBar.highlightColor[self.temp.flash[btn][1]])
		self.theme:SetFlash(button,r,g,b)
	else
		-- turn off all flash.
		self.theme:ClearFlash(button)
	end
end

function IBInfiniBar.prototype:ButtonFlash(btn)
	if not self.temp.flash then return nil end
	if not self.temp.flash[btn] then return nil end
	if not self.temp.flash[btn][1] then return nil end
	return true
end

function IBInfiniBar.prototype:SetOverlay(ovrlycode,set,btn)
	if not InfiniBar.ovrlyPriority[ovrlycode] then return end
	local button = self.btnframe[btn]
	if not button then return end
	self.temp.ovrly = self.temp.ovrly or {}
	self.temp.ovrlyon = self.temp.ovrlyon or {}
	self.temp.ovrly[btn] = self.temp.ovrly[btn] or {}
	self.temp.ovrlyon[btn] = self.temp.ovrlyon[btn] or {}
	if set then
		if not self.temp.ovrlyon[btn][ovrlycode] then
			self.temp.ovrly[btn][#self.temp.ovrly[btn]+1] = ovrlycode
			table.sort(self.temp.ovrly[btn],function(a,b) return InfiniBar.ovrlyPriority[a] < InfiniBar.ovrlyPriority[b] end)
			self.temp.ovrlyon[btn][ovrlycode] = true
		end
	elseif self.temp.ovrlyon[btn][ovrlycode] then
		for i,v in ipairs(self.temp.ovrly[btn]) do if v == ovrlycode then table.remove(self.temp.ovrly[btn],i) break end end
		self.temp.ovrlyon[btn][ovrlycode] = nil
	end
	if self.temp.ovrly[btn][1] then
		-- turn on the highest priority ovrly
		local r,g,b = unpack(InfiniBar.highlightColor[self.temp.ovrly[btn][1]])
		self.theme:SetOverlay(button,r,g,b)
	else
		-- turn off all ovrly.
		self.theme:ClearOverlay(button)
	end
end

function IBInfiniBar.prototype:HighlightBuff()
	for i,button in ipairs(self.btnframe) do
		-- find out what kind of buff this button needs to be highlighted
		local bName = self:GetButtonEffectiveState(i,"spell")
		if not bName then
			-- check to see if this is a macro, and if so, whether it is the spell being cast.
			local sMacroID = self:GetButtonEffectiveState(i,"macro")
			if not sMacroID then
				sMacroID = self:GetButtonEffectiveState(i,"macrotext")
			end
			if sMacroID then
				local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
				if mType == "spell" then
					bName = mData
				end
			end
		end
		if bName then
			local buff,_,sName,sRank = string.find(bName,'(.*)%(([^%)]*)%)$')
			if sRank == "" then sRank = nil end
			if not buff then
				sName = bName
				sRank = nil
			end
			if Aura:UnitHasBuff("player",sName,sRank) then
				self:SetFlash("Buffed",true,i)
			else
				self:SetFlash("Buffed",false,i)
			end
		else
			self:SetFlash("Buffed",false,i)
		end
	end
end

function IBInfiniBar.prototype:HighlightGear()
	for i,button in ipairs(self.btnframe) do
		-- find out what kind of buff this button needs to be highlighted
		local bName = self:GetButtonEffectiveState(i,"item")
		if not bName then
			-- check to see if this is a macro, and if so, whether it is the spell being cast.
			local sMacroID = self:GetButtonEffectiveState(i,"macro")
			if not sMacroID then
				sMacroID = self:GetButtonEffectiveState(i,"macrotext")
			end
			if sMacroID then
				local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
				if mType == "item" then
					bName = mData
				end
			end
		end
		if bName then
			if IsEquippedItem(bName) then
				self:SetFlash("Equipped",true,i)
			else
				self:SetFlash("Equipped",false,i)
			end
		else
			self:SetFlash("Equipped",false,i)
		end
	end
end

function IBInfiniBar.prototype:HighlightDebuff()
	for i,button in ipairs(self.btnframe) do
		-- find out what kind of buff this button needs to be highlighted
		local bName = self:GetButtonEffectiveState(i,"spell")
		if not bName then
			-- check to see if this is a macro, and if so, whether it is the spell being cast.
			local sMacroID = self:GetButtonEffectiveState(i,"macro")
			if not sMacroID then
				sMacroID = self:GetButtonEffectiveState(i,"macrotext")
			end
			if sMacroID then
				local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
				if mType == "spell" then
					bName = mData
				end
			end
		end
		if bName then
			local buff,_,sName,sRank = string.find(bName,'(.*)%(([^%)]*)%)$')
			if sRank == "" then sRank = nil end
			if not buff then
				sName = bName
				sRank = nil
			end
			if Aura:UnitHasBuff("target",sName,sRank) then
				self:SetFlash("DeBuffed",true,i)
			else
				self:SetFlash("DeBuffed",false,i)
			end
		else
			self:SetFlash("DeBuffed",false,i)
		end
	end
end

function IBInfiniBar.prototype:HighlightAutoCasting()
	for i,button in ipairs(self.btnframe) do
		local bName = self:GetButtonEffectiveState(i,"spell")
		if not bName then
			-- check to see if this is a macro, and if so, whether it is the spell being cast.
			local sMacroID = self:GetButtonEffectiveState(i,"macro")
			if not sMacroID then
				sMacroID = self:GetButtonEffectiveState(i,"macrotext")
			end
			if sMacroID then
				local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
				if mType == "spell" then
					bName = mData
				end
			end
		end
		if bName then
			if (bName == "Attack()" and self.temp.attackon) or IsAutoRepeatSpell(bName) then
				self:SetFlash("AutoCast",true,i)
			else
				self:SetFlash("AutoCast",false,i)
			end
		else
			self:SetFlash("AutoCast",false,i)
		end
	end
end

--[[function IBInfiniBar.prototype:UnhighlightAutoAttack()
	for i,button in ipairs(self.btnframe) do
		local bName = self:GetButtonEffectiveState(i,"spell")
		if bName == "Attack()" then
			self:SetFlash(i,"AutoCast",false)
		end
	end
end--]]

function IBInfiniBar.prototype:OverlayUsable()
	for i,button in ipairs(self.btnframe) do
		local bName = self:GetButtonEffectiveState(i,"spell")
		if not bName then
			-- check to see if this is a macro, and if so, whether it is the spell being cast.
			local sMacroID = self:GetButtonEffectiveState(i,"macro")
			if not sMacroID then
				sMacroID = self:GetButtonEffectiveState(i,"macrotext")
			end
			if sMacroID then
				local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
				if mType == "spell" then
					bName = mData
				end
			end
		end
		if bName then
			local use = {IsUsableSpell(bName)}
			if use[1] == 1 then
				self:SetOverlay("OutOfMana",false,i)
				self:SetOverlay("UnusableAction",false,i)
			elseif use[2] == 1 then
				self:SetOverlay("OutOfMana",true,i)
			else
				self:SetOverlay("UnusableAction",true,i)
			end
		else
			-- check if this is effectively an item, if so set it's usability
			bName = self:GetButtonEffectiveState(i,"item")
			if not bName then
				local sMacroID = self:GetButtonEffectiveState(i,"macro")
				if not sMacroID then
					sMacroID = self:GetButtonEffectiveState(i,"macrotext")
				end
				if sMacroID then
					local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
					if mType == "item" then
						bName = mData
					end
				end
			end
			if bName then
				local use = {IsUsableItem(bName)}
				if use[1] == 1 then
					self:SetOverlay("OutOfMana",false,i)
					self:SetOverlay("UnusableAction",false,i)
				elseif use[2] == 1 then
					self:SetOverlay("OutOfMana",true,i)
				else
					self:SetOverlay("UnusableAction",true,i)
				end
			else
				self:SetOverlay("OutOfMana",false,i)
				self:SetOverlay("UnusableAction",false,i)
			end
		end
	end
end

function IBInfiniBar.prototype:OverlayUpdate()
	local curState = self.header:GetAttribute("state")
	for i,button in ipairs(self.btnframe) do
		local ovrCooldown
		-- override cooldown
		local ovrtype = self.btndata[i]['ovrtype-'..curState]
		if ovrtype and self.btndata[i]['ovrCooldown-'..curState] then
			local ovraction = self.btndata[i]['ovr'..ovrtype..'-'..curState]
			if ovraction then
				ovrCooldown = true
				local s, d, e
				if ovrtype == 'spell' then
					s, d, e = GetSpellCooldown(ovraction)
				elseif ovrtype == 'pet' then
					s, d, e = GetPetActionCooldown(ovraction)
				else -- ovrtype == 'item'
					s, d, e = GetItemCooldown(ovraction)
				end
				if s and d and e then
					CooldownFrame_SetTimer(button.Cooldown,s,d,e)
				end
			end
		end
		
		local bName = self:GetButtonEffectiveState(i,"spell")
		if not bName then
			-- check to see if this is a macro, and if so, whether it is the spell being cast.
			local sMacroID = self:GetButtonEffectiveState(i,"macro")
			if not sMacroID then
				sMacroID = self:GetButtonEffectiveState(i,"macrotext")
			end
			if sMacroID then
				local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
				if mType == "spell" then
					bName = mData
				end
				-- Oddly enough, this is also a good place to update the macro's icon.
				local newTex = self:GetButtonTexture(i)
				if newTex then
					button.Icon:SetTexture(newTex)
					button.Icon:Show()
				else
					button.Icon:Hide()
				end
			end
		end
		if bName then
			if SpellHasRange(bName) then
				if IsSpellInRange(bName) ~= 0 then
					self:SetOverlay("NotInRange",false,i)
				else
					self:SetOverlay("NotInRange",true,i)
				end
			end
			if not ovrCooldown then
				local start, duration, enable = GetSpellCooldown(bName);
				if start and duration and enable then
					CooldownFrame_SetTimer(button.Cooldown,start,duration,enable)
				end
			end
		else
			-- check if this is effectively an item, if so set it's usability
			bName = self:GetButtonEffectiveState(i,"item")
			if not bName then
				local sMacroID = self:GetButtonEffectiveState(i,"macro")
				if not sMacroID then
					sMacroID = self:GetButtonEffectiveState(i,"macrotext")
				end
				if sMacroID then
					local mType, mData = IB_GetEffectiveMacroInfo(sMacroID)
					if mType == "item" then
						bName = mData
					end
				end
			end
			--override
			if bName then
				if ItemHasRange(bName) then
					if IsItemInRange(bName) ~= 0 then
						self:SetOverlay("NotInRange",false,i)
					else
						self:SetOverlay("NotInRange",true,i)
					end
				end
				if not ovrCooldown then
					local start, duration, enable = GetItemCooldown(bName);
					if start and duration and enable then
						CooldownFrame_SetTimer(button.Cooldown,start,duration,enable)
					end
				end
			else
				if not ovrCooldown then
					button.Cooldown:Hide()
				end
				self:SetOverlay("NotInRange",false,i)
			end
		end
	end
end

function IBInfiniBar.prototype:OverlayItem()
	for i,button in ipairs(self.btnframe) do
		local bItemID = self:GetButtonEffectiveState(i,"item")
		if bItemID then
			local bItemLink = self:GetButtonEffectiveState(i,"ibitem")
			if bItemLink then
				if GetItemCount(bItemLink) == 0 then
					self:SetOverlay("UnusableItem",true,i)
				else
					self:SetOverlay("UnusableItem",false,i)
				end
			else
				self:SetOverlay("UnusableItem",false,i)
			end
		else
			self:SetOverlay("UnusableItem",false,i)
		end
	end
end

function IBInfiniBar.prototype:UpdateTexts(btn,...)
	if not btn then
		for i in ipairs(self.btn) do
			self:UpdateTexts(i,...)
		end
	else
		local button = self.btnframe[btn]
		if not button then return end
		local tSubs = self:GetButtonEffectiveMetaData(btn,"textsubs")
		if not tSubs and self.btndata[btn] then tSubs = self.btndata[btn]["textsubs"] end
		if type(tSubs) == "table" then
			for i,v in pairs(tSubs) do
				local text = InfiniBar:ParseTextSub(v,self,btn,...)
				if text == "" or not text then
					button["Text"..i]:Hide()
				else
					button["Text"..i]:SetText(text)
					button["Text"..i]:Show()
				end
			end
		end
	end
end

function IBInfiniBar.prototype:ResetButtonBindings()
	for i,button in ipairs(self.btnframe) do
		self.btndata[i] = self.btndata[i] or {}
		local key = self.btndata[i].bind
		if key then
			SetBindingClick(key,button:GetName())
		end
	end
end

function IBInfiniBar.prototype:SetButtonBinding(btn,key)
	local button = self.btnframe[btn]
	self.btndata[btn] = self.btndata[btn] or {}
	local lastBinding = self.btndata[btn].bind
	if lastBinding then
		SetBinding(lastBinding)
	end
	if key then
		SetBindingClick(key,button:GetName())
	end
	self.btndata[btn].bind = key
end

function IBInfiniBar.prototype:GetButtonBinding(btn)
	return self.btndata[btn].bind
end

function IBInfiniBar.prototype:BuildButtonList()
	local t = {}
	for i,button in ipairs(self.btnframe) do
		t[i] = button:GetName()
	end
	return t
end

function IBInfiniBar.prototype:PerButton(func,...)
	for i in ipairs(self.btn) do
		func(self,i,...)
	end
end

local stateList = {'actionbar','stance','stealth','shift','ctrl','alt'}
local actionTypes = {'none','spell','pet','item','macro'}
local overrideTypes = {'none','spell','pet','item'}

function IBInfiniBar.prototype:BuildBarGUI(waterfall)
	InfiniBar.Label('Anchor X',50,waterfall)
	InfiniBar.NumBox(function() return string.format("%.2f",self.hdrState.headofsx) end,
		function(n) self.hdrState.headofsx = n self:UpdateHeaderState("headofsx") end,
		87.5,waterfall,'noNewLine',true)
	InfiniBar.PreLabel('Y',25,waterfall)
	InfiniBar.NumBox(function() return string.format("%.2f",self.hdrState.headofsy) end,
		function(n) self.hdrState.headofsy = n self:UpdateHeaderState("headofsy") end,
		87.5,waterfall,'noNewLine',true)
	InfiniBar.Checkbox('Bar Movement Locked',function() return not self.barDraggable end,
		function(v) self.barDraggable = not v end,250,waterfall)

	--InfiniBar.Label('Lock Position',175,waterfall)
	--InfiniBar.CheckBox(function

	InfiniBar.Heading("State Definitions",250,waterfall)

	-- Pages slider
	InfiniBar.Label('Actionbar Pages',100,waterfall)
	InfiniBar.Slider(function() return self.hdrStateDesc.pages or 1 end,
		function(v) self.hdrStateDesc.pages = tonumber(v) self:RefreshStates(self.hdrStateDesc) end,
		1,10,1,150,waterfall,'noNewLine',true,'fullRefresh',true)
	-- stances checkbox
	InfiniBar.Checkbox('Stance/Shapeshift',function() return self.hdrStateDesc.stance end,
		function(v) self.hdrStateDesc.stance = v self:RefreshStates(self.hdrStateDesc) end,250,waterfall,'fullRefresh',true)
	-- stealth checkbox
	InfiniBar.Checkbox('Stealth Pages',function() return self.hdrStateDesc.stealth end,
		function(v) self.hdrStateDesc.stealth = v self:RefreshStates(self.hdrStateDesc) end,250,waterfall,'fullRefresh',true)
	-- shift checkbox
	InfiniBar.Checkbox('Shift Pages',function() return self.hdrStateDesc.shift end,
		function(v) self.hdrStateDesc.shift = v self:RefreshStates(self.hdrStateDesc) end,250,waterfall,'fullRefresh',true)
	-- ctrl checkbox
	InfiniBar.Checkbox('Control Pages',function() return self.hdrStateDesc.ctrl end,
		function(v) self.hdrStateDesc.ctrl = v self:RefreshStates(self.hdrStateDesc) end,250,waterfall,'fullRefresh',true)
	-- alt checkbox
	InfiniBar.Checkbox('Alt Pages',function() return self.hdrStateDesc.alt end,
		function(v) self.hdrStateDesc.alt = v self:RefreshStates(self.hdrStateDesc) end,250,waterfall,'fullRefresh',true)
	InfiniBar.Heading("",200,waterfall)

	local btnList = {}
	for i in ipairs(self.btn) do
		btnList[i] = tostring(i)
	end
	InfiniBar.Label("Button",50,waterfall)
	InfiniBar.Dropdown(self.temp,'btnConfig',btnList,200,waterfall,function() self.temp.btnType = nil end,'noNewLine',true)
	InfiniBar.Button('Add Button',function()
			local newbtn = self:NewButton()
			self:SetButtonSize(36,36,newbtn)
			self:SetButtonLocation(0,0,newbtn)
			self:SetButtonMetaData(newbtn,"textsubs",{ [2] = "[key]", [4] = "[itemcount]" })
			self.temp.btnConfig = newbtn
		end,100,waterfall,'fullRefresh',true)
	InfiniBar.Label("",50,waterfall,'noNewLine',true)

	if self.temp.btnConfig and self.btn[tonumber(self.temp.btnConfig)] then
		local btnI = tonumber(self.temp.btnConfig)
		InfiniBar.Button('Delete Button',function()
				self:DeleteButton(btnI)
				if btnI > #self.btn then
					self.temp.btnConfig = #self.btn
				end
			end,100,waterfall,'noNewLine',true,'fullRefresh',true)

		InfiniBar.Label('Offset X',50,waterfall)
		InfiniBar.NumBox(function() return string.format("%.2f",self.btn[btnI].ofsx) end,
			function(n) self.btn[btnI].ofsx = n self:UpdateButtonState(btnI,"ofsx") end,
			87.5,waterfall,'noNewLine',true)
		InfiniBar.PreLabel('Y',25,waterfall)
		InfiniBar.NumBox(function() return string.format("%.2f",self.btn[btnI].ofsy) end,
			function(n) self.btn[btnI].ofsy = n self:UpdateButtonState(btnI,"ofsy") end,
			87.5,waterfall,'noNewLine',true)
		InfiniBar.Checkbox('Button Movement Locked',function() return not self.btndata[btnI].draggable end,
			function(v) self.btndata[btnI].draggable = not v end,250,waterfall)

		InfiniBar.Label('Scale',50,waterfall)
		InfiniBar.Slider(function() return self.btn[btnI].scale or 1 end,
			function(v) self.btn[btnI].scale = tonumber(v) self:UpdateButtonState(btnI,'scale') end,
			0.5,5,0.05,200,waterfall,'noNewLine',true)
		-- label, checkbox
		InfiniBar.Checkbox('Contents Locked',function() return self.btndata[btnI].contentsLock end,
			function(v) self.btndata[btnI].contentsLock = v end,250,waterfall,'fullRefresh',true)

		InfiniBar.Label('Keybind',75,waterfall)
		InfiniBar.Keybind(function() return self:GetButtonBinding(btnI) end,
			function(k) self:SetButtonBinding(btnI,k) end,150,waterfall,'noNewLine',true)

		for tsub = 1, 5 do
			InfiniBar.Label("Textsub "..tsub,50,waterfall)
			InfiniBar.TextBox(function() return self.btndata[btnI]["textsubs"][tsub] or "" end,
				function(v)
					self.btndata[btnI]["textsubs"][tsub] = v
					self:UpdateTexts(btnI)
				end,
				200,waterfall,'noNewLine',true)
		end

		local states = GenerateLongStateList(GenerateDescriptorFromOptions(self.hdrStateDesc))
		local states2 = GenerateStateList(GenerateDescriptorFromOptions(self.hdrStateDesc))
		local stateMap = {}
		for i,s1 in ipairs(states) do stateMap[s1] = states2[i] end
		states2 = nil
		InfiniBar.Label("State",50,waterfall)
		InfiniBar.Dropdown(self.temp,'btnState',states,200,waterfall,nil,'noNewLine',true)
		-- for current state
		if self.temp.btnState and stateMap[self.temp.btnState] then
			local curState = stateMap[self.temp.btnState]
			-- label, checkbox
			-- label, dropdown
			InfiniBar.Label("Override",50,waterfall)
			local ovrType = self.btndata[btnI]['ovrtype-'..curState]
			InfiniBar.DropdownFunc(function()
					return ovrType or 'none'
				end,
				function(v)
					if v == 'none' then
						v = nil
					end
					self.btndata[btnI]['ovrtype-'..curState] = v
				end,
				overrideTypes,200,waterfall,'noNewLine',true)
			if ovrType then
				InfiniBar.Checkbox('Override Icon',function() return self.btndata[btnI]['ovrIcon-'..curState] end,
					function(v) self.btndata[btnI]['ovrIcon-'..curState] = v self:UpdateButton(btnI) end,250,waterfall)
				InfiniBar.Checkbox('Override Tooltip',function() return self.btndata[btnI]['ovrTooltip-'..curState] end,
					function(v) self.btndata[btnI]['ovrTooltip-'..curState] = v self:UpdateButton(btnI) end,250,waterfall)
				InfiniBar.Checkbox('Override Cooldown',function() return self.btndata[btnI]['ovrCooldown-'..curState] end,
					function(v) self.btndata[btnI]['ovrCooldown-'..curState] = v self:UpdateButton(btnI) end,250,waterfall)
				-- list override options, based on type
				if ovrType == 'spell' then
					InfiniBar.Label("Spell",50,waterfall)
					InfiniBar.TextBox(function() return self.btndata[btnI]['ovrspell-'..curState] or "" end,
						function(v)
							self.btndata[btnI]['ovrspell-'..curState] = v
						end,200,waterfall,'noNewLine',true)
				elseif ovrType == 'pet' then
					InfiniBar.Label("Pet Spell",50,waterfall)
					InfiniBar.NumBox(function() return self.btndata[btnI]['ovrpet-'..curState] or "" end,
						function(v)
							self.btndata[btnI]['ovrpet-'..curState] = v
						end,200,waterfall,'noNewLine',true)
				elseif ovrType == 'item' then
					InfiniBar.Label("Item",50,waterfall)
					InfiniBar.TextBox(function() return self.btndata[btnI]['ovritem-'..curState] or "" end,
						function(v)
							self.btndata[btnI]['ovritem-'..curState] = v
						end,200,waterfall,'noNewLine',true)
				end
			end

			InfiniBar.Label("Action",50,waterfall)
			local curType = self.temp.btnType or self.btn[btnI]['type-'..curState]
			if self.btndata[btnI].contentsLock then
				InfiniBar.TextBox(function() return curType or 'none' end,
					function(v) end,
					200,waterfall,'noNewLine',true,'disabled',true)
			else
				InfiniBar.DropdownFunc(function()
						return curType or 'none'
					end,
					function(v)
						if v == 'none' then
							self:SetButtonAttribute(btnI,'type-'..curState,nil)
							v = nil
						end
						self.temp.btnType = v
					end,
					actionTypes,200,waterfall,'noNewLine',true)
			end
			-- label, actioncontrol
			if curType == 'spell' then
				InfiniBar.Label("Spell",50,waterfall)
				InfiniBar.TextBox(function() return self.btn[btnI]['spell-'..curState] end,
					function(v)
						self:SetButtonAttribute(btnI,'type-'..curState,curType)
						self:SetButtonAttribute(btnI,'spell-'..curState,v)
					end,
					200,waterfall,'noNewLine',true,'disabled',self.btndata[btnI].contentsLock)
			elseif curType == 'pet' then
				InfiniBar.Label("Pet Spell",50,waterfall)
				InfiniBar.NumBox(function() return self.btn[btnI]['pet-'..curState] end,
					function(v)
						self:SetButtonAttribute(btnI,'type-'..curState,curType)
						self:SetButtonAttribute(btnI,'pet-'..curState,v)
					end,
					200,waterfall,'noNewLine',true,'disabled',self.btndata[btnI].contentsLock)
			elseif curType == 'item' then
				--[[InfiniBar.Label("Item",50,waterfall)
				InfiniBar.NumBox(function() return self.btn[btnI]['item-'..curState] end,
					function(v)
						self:SetButtonAttribute(btnI,'type-'..curState,curType)
						self:SetButtonAttribute(btnI,'item-'..curState,v)
					end,
					200,waterfall,'noNewLine',true)
				InfiniBar.Label("Item",50,waterfall)
				InfiniBar.NumBox(function() return self.btn[btnI]['item-'..curState] end,
					function(v)
						self:SetButtonAttribute(btnI,'type-'..curState,curType)
						self:SetButtonAttribute(btnI,'item-'..curState,v)
					end,
					200,waterfall,'noNewLine',true)--]]
				InfiniBar.Label("Item",50,waterfall)
				InfiniBar.TextBox(function() return self.btn[btnI]['item-'..curState] end,
					function(v)
						self:SetButtonAttribute(btnI,'type-'..curState,curType)
						self:SetButtonAttribute(btnI,'item-'..curState,v)
					end,
					200,waterfall,'noNewLine',true,'disabled',self.btndata[btnI].contentsLock)
			elseif curType == 'macro' then
				InfiniBar.Label("Macro",50,waterfall)
				InfiniBar.NumBox(function() return self.btn[btnI]['macro-'..curState] end,
					function(v)
						self:SetButtonAttribute(btnI,'type-'..curState,curType)
						self:SetButtonAttribute(btnI,'macro-'..curState,v)
					end,
					200,waterfall,'noNewLine',true,'disabled',self.btndata[btnI].contentsLock)
			end
		end
	else
		InfiniBar.Button('Delete Button',function() end,100,waterfall,'disabled',true,'noNewLine',true)
	end
end

function IBInfiniBar.prototype:Serialize()
	local s = {}
	for k,v in pairs(self) do
		if type(v) ~= "function" and type(v) ~= "userdata" and k ~= "header" and k ~= "btnframe" and k ~= "temp" and k ~= "theme" then
			s[k] = v
		end
	end
	return s -- return table of serialized values
end

function IBInfiniBar:Deserialize(t)
	return self:new(t)
end

local function activate(self, oldLib, oldDeactivate)
    if oldDeactivate then
        oldDeactivate(oldLib)
    end
end

AceLibrary:Register(IBInfiniBar,"IBInfiniBar-1.0",1,activate)
